from peer import BTPeer
from peer import Node_failed
from distributed_kmeans import kmeans_node
import threading
import time
import sys
import socket
import os

arguments=sys.argv
if len(arguments)!=6:
   print "the program is terminated because input parameters are not efficient"
   print "Four parameters are required: threshold, neighborhood information, file name of local database, result file name"
   sys.exit(0)

listenport=arguments[1]
peermaster_filename=arguments[2]
database_filename=arguments[3]
threshold=float(arguments[4])
result_file_name=arguments[5]
#ip=arguments[6]
s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) 
s.connect( ( "www.google.com", 80 ) ) 
local_ip = s.getsockname()[0] 
s.close()

#neighbor_filename='peerone_neighbor'
try:
    peermaster_file = open(peermaster_filename,'r')
except IOError:
    print 'ioerror' 
global peermaster
peermaster=[]
for line in peermaster_file.readlines():
    tmp=line.split()
    element=[tmp[0],eval(tmp[1])]
    peermaster=element
peermaster_file.close()  
#print neighbor
#neighbor=[['127.0.0.1',10000],['127.0.0.1',10002]]#address information of neighbors in format of [ip,port]

#database_filename='database_nano.txt'#name of local database in .txt
#two inputs required for normal peer
#threshold=0.02


global_min=[]
global_max=[]
initial_state=[]
neighbor_centroids=[]
database_nano_input=[]
column_selected=[]
node_one=None
wait_counts={}
maxi_wait=2
database_last_record_time=os.stat(database_filename).st_mtime
ITERATION=1
virtual_centroids_count=0
centroids_counts={}
centroids={}
poll_iteration_record=0
pollsent=0
neighbor_iteration_reponses=[]
adding_history={}
initialized=False
neighbor=[]
message_pool={}
collect_result_check=True

'''for one in neighbor:
    neighbor_centroids.append({})'''
#print len(neighbor_centroids)

def return_min_max(BTconnection,data):
    #waiting for completing, connecting to database, and searching for the min and max value
    peercon=BTconnection
    min_max=[]
    mini=[]
 
    maxi=[]
    
    global column_selected
    column_selected=[int(x) for x in data.split(',')]

    database = open(database_filename,'r') 
    database_nano_input=[]
    for line in database.readlines():
        tmp=line.split()
        str2float=[float(x) for x in tmp]
        selected_data=[]
        for one in column_selected:
            selected_data.append(str2float[one])
        database_nano_input.append(selected_data)
    for one in range(len(database_nano_input[0])):
        tmp=[]
        for data in database_nano_input:
            tmp.append(data[one])
        mini.append(min(tmp))
        maxi.append(max(tmp))
    
    selected_min=[]
    selected_max=[]

    for one in column_selected:
        selected_min.append(mini[one])
        selected_max.append(maxi[one])

    min_max=selected_min+selected_max
    min_max_string = ','.join(str(n) for n in min_max)
    print min_max_string
    peercon.senddata('INIT', min_max_string)

def get_min_max(BTconnection,data):
    min_max= [float(n) for n in data.split(',')]
    length=len(min_max)
    count=0
    for data in min_max:
        if count<(0.5*length):
           global_min.append(data)
        else:
           global_max.append(data)
        count=count+1
    print 'node one'
    print global_min
    print global_max
def get_initstate(BTconnection,data):
    '''ini_data= [float(n) for n in data.split(',')]
    count=0
    print 'ini_data'
    print ini_data
    for data in ini_data:
        if count==0:
           tmp=[]
        if count<len(global_min):
           tmp.append(data)
           count=count+1
        else:
           initial_state.append(tmp)
           count=0
    initial_state.append(tmp) '''
    initial_state=eval(data)
    

    #instantiated kmeans_node
    database = open(database_filename,'r') 
    global database_nano_input
    database_nano_input=[]
    for line in database.readlines():
        tmp=line.split()
        str2float=[float(x) for x in tmp] 
        selected_data=[]
        for one in column_selected:
            selected_data.append(str2float[one])
        database_nano_input.append(selected_data)
    database.close()   # node_one database
    threshold=0.02
    #lock=threading.Lock()
    global node_one,filename
    #lock.acquire()
    node_one=kmeans_node(initial_state,database_nano_input,global_max,global_min,threshold)
    global centroids
    centroids[0]=node_one.centroids[0]
    normalized_file=open(filename,'w');
    for element in node_one.normaized_database:
        for one in element:
            normalized_file.write(str(one)+" ")
        normalized_file.write('\n')

def get_centroids(BTconnection,data):
    peercon=BTconnection
    num_of_iteration=int(data)
    global poll_iteration_record
    poll_iteration_record=num_of_iteration
    #if node_one != None:
       #length=len(node_one.centroids_counts)
    itera_str=str(num_of_iteration)
    if node_one == None:
       response=itera_str+'wait'
       peercon.senddata('RESP', response)
    else:
       #node_one.terminate_check(threshold)
       if(len(node_one.centroids)>1 and node_one.termination):
          tmp_centroids=node_one.centroids_counts.pop()
          node_one.centroids_counts.append(tmp_centroids)
          centroids_string = str(tmp_centroids) 
          response=itera_str+centroids_string
          peercon.senddata('RESP', response)
       else:
          if not centroids_counts.has_key(num_of_iteration-1):
             response=itera_str+'wait'
             peercon.senddata('RESP', response)
          else:
             centroids=centroids_counts[num_of_iteration-1]
             centroids_string = str(centroids) 
             response=itera_str+centroids_string
             peercon.senddata('RESP', response)
       
def sendPoll():
    
    while(True):
       global pollsent
       if node_one == None or initialized == False:
         
          continue
       else:
          global ITERATION
          #print ITERATION
          #node_one.terminate_check(threshold)
          if(len(node_one.centroids)>1 and node_one.termination):
             #print "while loop is broken because of sendpoll"
             #break
             continue
          else:
             if (virtual_centroids_count == ITERATION):
                iteration=virtual_centroids_count
                if iteration > pollsent: 
                   #print 'inside sendpoll'
                   #print iteration
                   for peer in neighbor:
                       '''try:
                          s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                          s.connect((peer[0],peer[1]))
                          s.close()
                       except:
                          print 'fail to connect to the peer'
                          neighbor.remove(peer)
                          print neighbor
                          pass
                          return
                       else:'''
                       try:
                           peer_one.connectandsend(peer[0], peer[1], 'POLL', str(iteration) , pid=None, waitreply=True )
                       except Node_failed:
                           print "node", 
                           print peer[0]
                           print peer[1]
                           print "failed"
                           global neighbor
                           neighbor.remove([peer[0],peer[1]])
                           print neighbor
                   pollsent=iteration
       

def addCentroids():
    count=0
    while(True):
       if node_one==None or initialized == False:
          continue
       else:
          #node_one.terminate_check(threshold)
          if(len(node_one.centroids)>1 and node_one.termination):
             #print "while loop is broken because of addcentroids"
             #break
             continue
          else:
             global ITERATION,centroids
             itera=str(ITERATION)
             neighbor_data=[]
             count=0
             num_of_neighbor=len(neighbor)
             for one in neighbor_centroids:
                 if one.has_key(itera):
                    count=count+1
                    neighbor_data.append(one[itera])
             if count==num_of_neighbor and centroids_counts.has_key(eval(itera)-1):
                neighbor_data.append(centroids_counts[eval(itera)-1])
                node_one.weighted_centroid_calculate(neighbor_data,num_of_neighbor)
                centroids[eval(itera)]=node_one.centroids.pop()
                node_one.centroids.append(centroids[eval(itera)])
                ITERATION=ITERATION+1
                node_one.terminate_check(threshold)
                count=count+1
                print 'adding centroids in iteration',
               
                print node_one.centroids
       
       

def store_neibor_centroids(BTconnection,data):
    peercon=BTconnection
    neighbor_ip,neighbor_port=peercon.s.getpeername()
    print neighbor
    print [neighbor_ip,neighbor_port]
    #print type(neighbor_ip)
    if 'wait' in data:
       time.sleep(5)
       data_list=data.split('w',1)
       itera=data_list[0]
       if wait_counts.has_key(itera):
           wait_counts[itera]=wait_counts[itera]+1
       if not wait_counts.has_key(itera):
           wait_counts[itera]=1
       if wait_counts[itera]<= maxi_wait:
           '''try:
              s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
              s.connect((neighbor_ip,neighbor_port))
              s.close()
           except:
                  print neighbor
                  print [neighbor_ip,neighbor_port]
                  print 'fail to connect to the peer'
                  neighbor.remove([neighbor_ip,neighbor_port])
                  #print neighbor
                  return
           else:'''
           try:
               peer_one.connectandsend(neighbor_ip, neighbor_port, 'POLL', str(itera) , pid=None, waitreply=True )
           except Node_failed:
               print "node", 
               print neighbor_ip
               print neighbor_port
               print "failed"
               global neighbor
               neighbor.remove([neighbor_ip,neighbor_port])
               print neighbor
       
    else:
       data_list=data.split('[',1)
       length=len(data)
       itera=data_list[0]
       centroids_str='['+data_list[1]
       tmp_centroids=eval(centroids_str)
       count=0
       for one in neighbor:
           if one[0]==neighbor_ip and one[1]==neighbor_port:
              break  
           count=count+1
       neighbor_centroids[count][itera]=tmp_centroids
    #print neighbor_centroids
         
    

def calculate_centroids():
    count=0
    while(True):
       if node_one == None or initialized == False:
          continue
       else:
          global virtual_centroids_count, ITERATION, centroids_count,centroids
          #node_one.terminate_check(threshold)
          if(len(node_one.centroids)>1 and node_one.termination and ITERATION ==virtual_centroids_count):
             #print "while loop is broken because of calculate_centroids"
             #break
             continue
          else:
             
             if virtual_centroids_count < ITERATION:#len(node_one.centroids):
                if virtual_centroids_count >= count:
                   itera=virtual_centroids_count
                   print centroids
                   print itera
                   new_centroids=node_one.one_round_kmeans(centroids[itera])
                   node_one.local_centroid_counts(new_centroids)
                   
                   centroids_counts[itera]=node_one.centroids_counts.pop()
                   print centroids_counts
                   node_one.centroids_counts.append(centroids_counts[itera])
                   virtual_centroids_count=virtual_centroids_count+1
                   print node_one.centroids_counts
                   count=count+1

def collect_result():
    global collect_result_check
    while (True):
       if node_one==None:
          continue
       else:
          if (len(node_one.centroids)>1 and node_one.termination and ITERATION == virtual_centroids_count) and collect_result_check == True:
            
             final=len(node_one.centroids_counts)-1
             cluster=node_one.centroids_counts[final]
             print cluster
             '''denormalization = lambda x,y,z : x*(y-z)+z
             denormalized_final_cluster=[]
             for one in cluster:
                 tmp=map(denormalization,one[0],global_max,global_min)
                 denormalized_final_cluster.append(tmp)'''
             #filename="node_one_result.txt"
             FILE=open(result_file_name,"w")
             tmp_string="final cluster is"
             FILE.write(tmp_string+'\n')
             FILE.write(str(cluster)+'\n')
             node_one_cluster,node_one_distance,node_one_clustered=node_one.final_cluster();
             num=len(node_one_cluster)
             for i in range(num):
                 tmp_str=str(database_nano_input[i])+'   '+str(node_one_cluster[i])+'   '+str(node_one_distance[i])+'\n'
                 FILE.write(tmp_str)
             #FILE.write(str(node_one_cluster)+'\n')
             #FILE.write('the distance of every node to its centre'+'\n')
             #FILE.write(str(node_one_distance)+'\n')
             #FILE.write('lists of nodes in different cluster'+'\n')
             #FILE.write(str(node_one_clustered)+'\n')
             FILE.close()
             global centroids, centroids_counts, collect_result_check
             print "final centroids and centroids_counts"
             print centroids
             print centroids_counts
             collect_result_check=False
         
def database_check():
    while(True):
        global database_last_record_time, node_one,collect_result_check
        database_lastchange_time=os.stat(database_filename).st_mtime
        #print database_last_record_time
        #print database_lastchange_time
        if (database_lastchange_time==database_last_record_time):
            continue
        else:
            
            database = open(database_filename,'r') 
            database_input=[]
            for line in database.readlines():
                tmp=line.split()
                str2float=[float(x) for x in tmp] 
                selected_data=[]
            for one in column_selected:
                selected_data.append(str2float[one])
            database_input.append(selected_data)
            database.close()   # node_one database
            node_one.database_change(database_input)
            
            #print database_last_record_time
            database_last_record_time=database_lastchange_time
           #print database_last_record_time
            # Reset ITERATION and virtual_centroids_count, let node becomes active again
            ITERATION=poll_iteration_record
            virtual_centroids_count=ITERATION
            print virtual_centroids_count
            print ITERATION
            #new start state
            tmp_centroids=node_one.centroids.pop()
            node_one.centroids.append(tmp_centroids)
            new_centroids=node_one.one_round_kmeans(tmp_centroids)
            node_one.local_centroid_counts(new_centroids)
            centroids_counts[ITERATION-1]=node_one.centroids_counts.pop()
            node_one.termination=False
            collect_result_check=True
            for peer in neighbor:
                peer_one.connectandsend(peer[0], peer[1], 'CHAN', str([local_ip,eval(listenport)])+';'+str([local_ip,eval(listenport)])+';'+'UPDATING'  , pid=None, waitreply=False)
            global message_poll
            message_pool[local_ip]='UPDATING'

def up_to_date(BTconnection,data):
    message=data.split(';')
    meesage_source=eval(message[0])
    changing_source=eval(message[1])
    changing_reason=message[2]
    print meesage_source
    print changing_source
    print changing_reason
    
    if message_pool.has_key(changing_source[0]) and (changing_reason in message_pool[changing_source[0]]):
        print "already saw this message"
        pass
    else:
        ITERATION=poll_iteration_record
        virtual_centroids_count=ITERATION
        #print virtual_centroids_count
        #print ITERATION
        #new start state
        tmp_centroids=node_one.centroids.pop()
        node_one.centroids.append(tmp_centroids)
        new_centroids=node_one.one_round_kmeans(tmp_centroids)
        node_one.local_centroid_counts(new_centroids)
        centroids_counts[ITERATION-1]=node_one.centroids_counts.pop()
    
        global neighbor
        print neighbor
        #print source_ip
        #print source_port
        global message_pool
    
 
        node_one.termination=False
        global collect_result_check
        collect_result_check=True
        neighbor_rest=[]
        for one in neighbor:
            neighbor_rest.append(one)
        neighbor_rest.remove(meesage_source)
        print neighbor_rest
        for peer in neighbor_rest:
            peer_one.connectandsend(peer[0], peer[1], 'CHAN', str([local_ip,eval(listenport)])+';'+str(changing_source)+';'+changing_reason, pid=None, waitreply=False)
        message_pool[changing_source[0]]=changing_reason 

def neighbor_information_handler(BTconnection,data):
    neighbor_information=data.split(';')
    global neighbor, neighbor_centroids
    for one in neighbor_information:
        neighbor.append(eval(one))
        neighbor_centroids.append({})
        
    print neighbor
    
def add_request_handler(BTconnection,data):
    peercon=BTconnection
    #ip_port=data.split(' ',1)
    ip,port=peercon.s.getpeername()
    port=eval(data)
    global neighbor,ITERATION,centroids
    #neighbor.append([ip,port,ITERATION+1])
    if not node_one.terminate_check:
        response=str(ITERATION)+','+str(centroids[ITERATION])+','+ str(column_selected)
    else:
        last_iteration=max(k for k, v in centroids.iteritems())
        last_centroids=centroids[last_iteration]
        response=str(poll_iteration_record)+','+str(last_centroids)+','+str(column_selected)
    
    neighbor_rest=neighbor
    print neighbor_rest
    for peer in neighbor_rest:
        peer_one.connectandsend(peer[0], peer[1], 'CHAN', str([local_ip,eval(listenport)])+';'+str([ip,port])+';'+'ADDING', pid=None, waitreply=False)
    
    global message_pool
    message_pool[ip]='ADDING'
        
    global adding_history
    if adding_history.has_key(ITERATION):
        adding_history[ITERATION].append([ip,port])
    else:
        adding_history[ITERATION]=[[ip,port]]
        
    peer_one.connectandsend(ip, port, 'ADDR', response, pid=None, waitreply=False)
    
#def node_admit(BTconnection,data): 
    #pass;
    #everything starts from here
def add_reponse_handler(BTconnection,data):
    if not 'terminated' in data:
        iteration_centroids=data.split(';')
        global neighbor_iteration_reponses
        neighbor_iteration_reponses.append(iteration_centroids)
    else:
        neighbor_iteration_reponses.append([])
    print neighbor_iteration_reponses

def node_initialize():
    global neighbor_iteration_reponses, ITERATION, neighbor
    while(True):
        if (len(neighbor) !=0 and len(neighbor)==len(neighbor_iteration_reponses)) :
            break
    print "break in node initialization"
    print len(neighbor)
    print len(neighbor_iteration_reponses)
    iterations=[]
    
    #neighbor_iteration_reponses=[['1', '[[1.0, 0.024060887137000001, 0.0862222596955, 0.091658386464600006, 0.21446808510599999], [1.0, 0.66876609659200004, 0.157144833181, 0.073489553144999997, 0.16434782608699999], [1.0, 0.028726059144300001, 0.24547797433599999, 0.061410808423700003, 0.0], [1.0, 0.031870671490600001, 0.55587143967200003, 0.18173249427300001, 0.0]]' ,'[0,1,2,3,4]'], ['2', '[[0.51552273871457843, 0.027209682425403593, 0.09026867711965228, 0.1319885989899244, 0.1303642145484219], [0.95000000000004992, 0.70655032694550002, 0.23115488060236999, 0.107931444527215, 0.14577384743492], [0.45015105740196604, 0.028322071403296487, 0.096961952162567241, 0.074989959968812595, 0.011256350995114638], [0.86363636363649998, 0.077251250959974677, 0.53906358678887656, 0.23661706072876038, 0.085740867691357789]]', '[0,1,2,3,4]']]
    for one in neighbor_iteration_reponses:
        if one != []:
            iterations.append(int(one[0]))
    
    minimum_iteration=min(iterations)
    
    ITERATION=minimum_iteration
    print ITERATION
    
    for one in neighbor_iteration_reponses:
        if minimum_iteration == int(one[0]):
            initial_state = eval(one[1])
            
    global column_selected
    column_selected = eval(neighbor_iteration_reponses[0][2])
    database = open(database_filename,'r') 
    global database_nano_input
    database_nano_input=[]
    for line in database.readlines():
        tmp=line.split()
        str2float=[float(x) for x in tmp] 
        selected_data=[]
        for one in column_selected:
            selected_data.append(str2float[one])
        database_nano_input.append(selected_data)
    database.close()   # node_one database
    
    global node_one
    node_one=kmeans_node(initial_state,database_nano_input,threshold)
    
    global centroids
    for i in range(ITERATION): 
        centroids[i]= initial_state
    #print centroids
        
    global centroids_counts
    new_centroids=node_one.one_round_kmeans(initial_state)
    node_one.local_centroid_counts(new_centroids)
    tmp=node_one.centroids_counts.pop()
    node_one.centroids_counts.append(tmp)
    for i in range(ITERATION-1): 
        centroids_counts[i]= tmp
    virtual_centroids_count=len(centroids_counts)
    print virtual_centroids_count
    #print centroids_counts
    global initialized
    initialized=True

    
peer_one=BTPeer(5,listenport,None,local_ip);
#peer_one.addhandler('INIT',return_min_max)
#get global mini and maxi
#peer_one.addhandler('GLOB',get_min_max)

#using global_min and global_max to normalization local database
#~~~~~~~~

#get the initial state and built itsown database
#peer_one.addhandler('STAR',get_initstate)


peer_one.addhandler('POLL', get_centroids)
peer_one.addhandler('RESP', store_neibor_centroids)
peer_one.addhandler('CHAN', up_to_date)
peer_one.addhandler('ADDR', neighbor_information_handler)
peer_one.addhandler('ADDN', add_request_handler)
peer_one.addhandler('ADDI', add_reponse_handler)
#peer_one.addhandler('ADMI', node_admit)
#initialize = threading.Thread( target = node_initialize, args = [] )
mainloop = threading.Thread( target = peer_one.mainloop, args = [] ) 
calculating=threading.Thread(target = calculate_centroids, args = [] ) 
sendpoll=threading.Thread(target = sendPoll, args = [] )
addcentroids=threading.Thread(target=addCentroids,args=[])
output_result=threading.Thread(target=collect_result,args=[])
database_update_check=threading.Thread(target=database_check,args=[])
node_initialize=threading.Thread(target=node_initialize,args=[])

mainloop.start()
node_initialize.start()
global initialized

    
calculating.start()
sendpoll.start()
addcentroids.start()
output_result.start()
#database_update_check.start()
global peermaster
master_ip=peermaster[0]
master_port=peermaster[1]
string_to_send=str(listenport)
peer_one.connectandsend(master_ip, master_port, 'ADD ',string_to_send, pid=None, waitreply=True )

while (len(neighbor)==0):
    continue

for one in neighbor:
    neighbor_ip=one[0]
    neighbor_port=one[1]
    string_to_send=str(listenport)
    peer_one.connectandsend(one[0], one[1], 'ADDN',string_to_send, pid=None, waitreply=True )
